﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Security.Cryptography;
using System.IO;
using iTunesLib;
using System.Web;

namespace iTunesFastForward.LastFM
{

    class TracksSubmittedEventArgs : EventArgs
    {
        public TrackSubmission[] SubmittedTracks { get; set; }
    }

    class LastFMNotifiateEventArgs : EventArgs
    {
        public string Notification { get; set; }
    }

    class LastFMManager
    {

        public delegate void TracksSubmittedEventHandler(object sender, TracksSubmittedEventArgs e);
        public delegate void LastFMNotifiateEventHandler(object sender, LastFMNotifiateEventArgs e);

        public event TracksSubmittedEventHandler TracksSubmitted;
        public event LastFMNotifiateEventHandler LastFMNotifiate;

        public string Username { get; set; }

        public string PasswordHash { get; set; }

        public string SessionID { get; private set; }
        public string NowPlayingURL { get; private set; }
        public string SubmissionURL { get; private set; }

        public bool HandShaked { get; set; }

        private TrackSubmission _playingTrack;
        private TrackSubmission _lastPlayedTrack;
        private DateTime _startingPlayingTrack;
        private object _lock = new object();

        private void OnTracksSubmitted(List<TrackSubmission> tracks)
        {
            if (TracksSubmitted != null)
                TracksSubmitted(this, new TracksSubmittedEventArgs { SubmittedTracks = tracks == null ? null : tracks.ToArray() });
        }

        private void OnLastFMNotifiate(string notification)
        {
            if (LastFMNotifiate != null)
                LastFMNotifiate(this, new LastFMNotifiateEventArgs { Notification = notification });
        }

        private string SubmitRequestAndGetResponse(string uri, string args, bool post)
        {
            Log.Instance.WriteLine(string.Format("[last.fm] {0}?{1}", uri, args));

            try
            {
                return Utils.SubmitRequestAndGetResponse(uri, args, post);
            }
            catch (Exception)
            {
                return null;
            }
        }

        public void HandShake()
        {
            lock (_lock)
            {
                string requestStr = "http://post.audioscrobbler.com/";

                string args = "hs=true&p=1.2&c={0}&v={1}&u={2}&t={3}&a={4}";

                double timeStamp = GetUNIXTimeStamp();

                args = string.Format(args, "iff", "0.1", HttpUtility.UrlEncode(Username), timeStamp, GetAuthenticationKey(timeStamp));

                Log.Instance.WriteLine("[last.fm] Sending handshake");

                string result = SubmitRequestAndGetResponse(requestStr, args, false);

                if (result == null)
                {
                    OnLastFMNotifiate("Cannot connect to server");
                    return;
                }

                string[] lines = result.Split('\n');

                Log.Instance.WriteLine("[last.fm] Result: " + lines[0]);

                if (lines[0] == "OK")
                {
                    HandShaked = true;
                    SessionID = lines[1];
                    NowPlayingURL = lines[2];
                    SubmissionURL = lines[3];
                    Log.Instance.WriteLine(string.Format("[last.fm] Result (session ID, NowPlayingURL, SubmissionURL): {0}, {1}, {2}", lines[1], lines[2], lines[3]));
                }
                else
                    HandShaked = false;

                OnLastFMNotifiate(lines[0]);
            }
        }

        private double GetUNIXTimeStamp()
        {
            DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            TimeSpan diff = DateTime.UtcNow - origin;
            return Math.Floor(diff.TotalSeconds);
        }

        private double GetUNIXTimeStamp(DateTime date)
        {
            DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            TimeSpan diff = date - origin;
            return Math.Floor(diff.TotalSeconds);
        }

        public string SetPassword(string input)
        {
            string hash = GetMD5Hash(input);

            this.PasswordHash = hash;
            iTFFSettings.Instance.LastFMPasswordHash = PasswordHash;
            iTFFSettings.Instance.LastFMPasswordLength = input.Length;
            return hash;
        }

        private string GetMD5Hash(string input)
        {
            // step 1, calculate MD5 hash from input
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.Default.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            // step 2, convert byte array to hex string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("x2"));
            }
            return sb.ToString();
        }

        private string GetAuthenticationKey(double timeStamp)
        {
            return GetMD5Hash(PasswordHash + timeStamp);
        }

        private void NowPlayingNotifiate(TrackSubmission track)
        {
            string args = string.Format("s={0}&a={1}&t={2}&b={3}&l={4}&n={5}", SessionID, HttpUtility.UrlEncode(track.Artist), HttpUtility.UrlEncode(track.TrackName), HttpUtility.UrlEncode(track.Album), track.Seconds, track.TrackNumber);


            Log.Instance.WriteLine("[last.fm] Sending NowPlayingNotifiate");

            string result = SubmitRequestAndGetResponse(NowPlayingURL, args, true).Trim();


            if (result == null)
            {
                OnLastFMNotifiate("Cannot connect to server");
                return;
            }

            Log.Instance.WriteLine("[last.fm] Result: " + result);

            if (result == "BADSESSION")
                HandShaked = false;

            OnLastFMNotifiate(result);
        }

        /// <summary>
        /// Notify whenever starting playing a file
        /// </summary>
        /// <param name="track"></param>
        public void BeginPlay(IITTrack track)
        {
            lock (_lock)
            {
                if (track == null)
                    return;

                if (!HandShaked)
                    HandShake();

                if (HandShaked)
                    NowPlayingNotifiate(TrackSubmission.FromIITTrack(track));

                // ne pas envoyer de track de moins de 30 secondes
                if (track.Duration <= 30)
                    return;

                TrackSubmission actualTrack = TrackSubmission.FromIITTrack(track);

                // si c'est la même track que précédemment mais que ca redémarre à zéro (lecture en boucle), ou bien si c'est simplement une autre track, faire le traitement
                if (
                    (_lastPlayedTrack == actualTrack && iTunesActions.Instance.PlayerPosition < 2)
                    || _lastPlayedTrack != actualTrack)
                {
                    _playingTrack = actualTrack;
                    _startingPlayingTrack = DateTime.Now;
                }

            }
        }


        /// <summary>
        /// Notify periodically
        /// </summary>
        /// <param name="track">Actual playing track</param>
        public void CheckTime(IITTrack track)
        {
            lock (_lock)
            {
                var submissionTrack = TrackSubmission.FromIITTrack(track);

                // vérifier que la track n'a pas changé
                if (_playingTrack != null && _playingTrack.Equals(submissionTrack))
                {
                    // vérifier qu'il y ai soit 240 secondes d'écoulées soit la moitié du temps de la chanson
                    if ((DateTime.Now - _startingPlayingTrack).TotalSeconds >= 240 ||
                        (_playingTrack.Seconds > 0 && (DateTime.Now - _startingPlayingTrack).TotalSeconds >= (int)_playingTrack.Seconds / 2 + 1))
                    {
                        _lastPlayedTrack = _playingTrack;
                        _playingTrack = null;
                        submissionTrack.StartedPlayingDate = DateTime.UtcNow;
                        AddToQueue(submissionTrack);
                    }
                }
                else if (!iTunesActions.Instance.COMCallDisabled && iTunesActions.Instance.PlayerState == ITPlayerState.ITPlayerStatePlaying && !submissionTrack.Equals(_lastPlayedTrack))
                    BeginPlay(track);

                if (HandShaked)
                    ProcessQueue();
            }
        }


        private void AddToQueue(TrackSubmission track)
        {
            if (iTFFSettings.Instance.LastFMSongsQueuedForSubmission == null)
                iTFFSettings.Instance.LastFMSongsQueuedForSubmission = new List<TrackSubmission>();
            iTFFSettings.Instance.LastFMSongsQueuedForSubmission.Add(track);
            iTFFSettings.Instance.Save();
            OnTracksSubmitted(null);
        }


        private void ProcessQueue()
        {
            lock (_lock)
            {
                if (iTFFSettings.Instance.LastFMSongsQueuedForSubmission == null || iTFFSettings.Instance.LastFMSongsQueuedForSubmission.Count == 0)
                    return;

                // parcourir les éléments un à un
                List<string> tracks = new List<string>();

                int i = 0;

                foreach (var track in iTFFSettings.Instance.LastFMSongsQueuedForSubmission)
                {
                    tracks.Add(string.Format(
                        "a[{0}]={1}&t[{0}]={2}&i[{0}]={3}&o[{0}]={4}&r[{0}]=&l[{0}]={5}&b[{0}]={6}&n[{0}]={7}&m[{0}]=",
                        i, // track submission number
                        HttpUtility.UrlEncode(track.Artist ?? string.Empty),
                        HttpUtility.UrlEncode(track.TrackName ?? string.Empty),
                        GetUNIXTimeStamp(track.StartedPlayingDate),
                        "P",
                        track.Seconds,
                        HttpUtility.UrlEncode(track.Album ?? string.Empty),
                        track.TrackNumber));

                    i++;
                }

                string args = string.Format("s={0}&{1}", SessionID, string.Join("&", tracks.ToArray()));

                Log.Instance.WriteLine("[last.fm] Sending submission");

                string result = SubmitRequestAndGetResponse(this.SubmissionURL, args, true);

                if (result == null)
                {
                    OnLastFMNotifiate("Cannot connect to server");
                    return;
                }

                result = result.Trim();

                Log.Instance.WriteLine("[last.fm] Result: " + result);


                //iTFFSettings.Instance.LastFMSubmissionAttempts++;


                if (result == "OK")
                {
                    var tracksSubmitted = new List<TrackSubmission>(iTFFSettings.Instance.LastFMSongsQueuedForSubmission);
                    iTFFSettings.Instance.LastFMSongsSubmitted += tracksSubmitted.Count;
                    iTFFSettings.Instance.LastFMSongsQueuedForSubmission.Clear();
                    OnTracksSubmitted(tracksSubmitted);
                }
                else if (result == "BADSESSION")
                    HandShaked = false;

                OnLastFMNotifiate(result);
            }
        }
    }
}
